﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.Debugger.Interop;
using System.Diagnostics;
using UDKDevKitVSXDebugEngine.Definitions;
using UDKDevKitVSXDebugEngine.Extentions;
using UDKDevKitVSXCore.Classes;

namespace UDKDevKitVSXDebugEngine
{
    // This class represents a breakpoint that has been bound to a location in the debuggee. It is a child of the pending breakpoint
    // that creates it. Unless the pending breakpoint only has one bound breakpoint, each bound breakpoint is displayed as a child of the
    // pending breakpoint in the breakpoints window. Otherwise, only one is displayed.
    public class BoundBreakpoint : IDebugBoundBreakpoint2
    {
        private PendingBreakpoint pendingBreakpoint;
        private BreakpointResolution breakpointResolution;
        private Engine engine;

        private bool enabled;
        private bool deleted;

        private uint hitCount;
        private BP_CONDITION bpCondition;
        private BP_PASSCOUNT bpPasscountStyle;

        private string originalConditionValue;

        internal BoundBreakpoint(Engine engine, PendingBreakpoint pendingBreakpoint, BreakpointResolution breakpointResolution)
        {
            if (engine == null)
            {
                Debug.Fail("ArgumentNullException - engine");
                throw new ArgumentNullException("engine");
            }

            if (pendingBreakpoint == null)
            {
                Debug.Fail("ArgumentNullException - pendingBreakpoint");
                throw new ArgumentNullException("pendingBreakpoint");
            }

            if (breakpointResolution == null)
            {
                Debug.Fail("ArgumentNullException - breakpointResolution");
                throw new ArgumentNullException("breakpointResolution");
            }

            this.engine = engine;
            this.pendingBreakpoint = pendingBreakpoint;
            this.breakpointResolution = breakpointResolution;
            this.enabled = true;
            this.deleted = false;
            this.hitCount = 0;
        }

        internal bool BelongTo(string className, uint line)
        {
            var documentContext = this.breakpointResolution.DocumentContext;
            if (documentContext == null)
            {
                return false;
            }

            return documentContext.ClassName.Equals(className)
                && documentContext.IsInReange(line);
        }

        internal bool ShouldBeRaised
        {
            get
            {
                switch (bpPasscountStyle.stylePassCount)
                {
                    case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL:
                        return this.hitCount == bpPasscountStyle.dwPassCount;
                    case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL_OR_GREATER:
                        return this.hitCount >= bpPasscountStyle.dwPassCount;
                    case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_MOD:
                        return this.hitCount % bpPasscountStyle.dwPassCount == 0;
                }

                switch (bpCondition.styleCondition)
                {
                    case enum_BP_COND_STYLE.BP_COND_WHEN_CHANGED:
                        var current = this.GetEval(this.bpCondition.bstrCondition);
                        if (this.originalConditionValue != current)
                        {
                            this.originalConditionValue = current;
                            return true;
                        }

                        break;
                    case enum_BP_COND_STYLE.BP_COND_WHEN_TRUE:
                        return true.Equals(new EqualExpression(this.bpCondition.bstrCondition, this.engine).Evaluate());
                }

                return true;
            }
        }

        #region IDebugBoundBreakpoint2 Members

        // Called when the breakpoint is being deleted by the user.
        int IDebugBoundBreakpoint2.Delete()
        {
            if (!deleted)
            {
                deleted = true;
                pendingBreakpoint.OnBoundBreakpointDeleted(this);
            }

            return Constants.S_OK;
        }

        // Called by the debugger UI when the user is enabling or disabling a breakpoint.
        int IDebugBoundBreakpoint2.Enable(int fEnable)
        {
            this.enabled = fEnable == 0 ? false : true;
            return Constants.S_OK;
        }

        // Return the breakpoint resolution which describes how the breakpoint bound in the debuggee.
        int IDebugBoundBreakpoint2.GetBreakpointResolution(out IDebugBreakpointResolution2 ppBPResolution)
        {
            ppBPResolution = this.breakpointResolution;
            return Constants.S_OK;
        }

        // Return the pending breakpoint for this bound breakpoint.
        int IDebugBoundBreakpoint2.GetPendingBreakpoint(out IDebugPendingBreakpoint2 ppPendingBreakpoint)
        {
            ppPendingBreakpoint = this.pendingBreakpoint;
            return Constants.S_OK;
        }

        int IDebugBoundBreakpoint2.GetState(enum_BP_STATE[] pState)
        {
            pState[0] = 0;

            if (deleted)
            {
                pState[0] = enum_BP_STATE.BPS_DELETED;
            }
            else if (enabled)
            {
                pState[0] = enum_BP_STATE.BPS_ENABLED;
            }
            else if (!enabled)
            {
                pState[0] = enum_BP_STATE.BPS_DISABLED;
            }

            return Constants.S_OK;
        }

        int IDebugBoundBreakpoint2.GetHitCount(out uint pdwHitCount)
        {
            pdwHitCount = this.hitCount;
            return Constants.S_OK;
        }

        int IDebugBoundBreakpoint2.SetCondition(BP_CONDITION bpCondition)
        {
            this.bpCondition = bpCondition;

            // init original value
            if (this.bpCondition.styleCondition == enum_BP_COND_STYLE.BP_COND_WHEN_CHANGED)
            {
                this.originalConditionValue = this.GetEval(this.bpCondition.bstrCondition);
            }

            //GoogleAnalitics.TrackEvent("Debugger", "SetBreakpointCondition", "Set Breakpoint Condition", 1);

            return Constants.S_OK;
        }

        private string GetEval(string bstrCondition)
        {
            var eval = new DebuggerExpression(bstrCondition, this.engine).Evaluate();
            if (eval != null)
            {
                return eval.ToString();
            }

            return String.Empty;
        }

        int IDebugBoundBreakpoint2.SetHitCount(uint dwHitCount)
        {
            this.hitCount = dwHitCount;
            return Constants.S_OK;
        }

        int IDebugBoundBreakpoint2.SetPassCount(BP_PASSCOUNT bpPassCount)
        {
            this.bpPasscountStyle = bpPassCount;

            //GoogleAnalitics.TrackEvent("Debugger", "SetBreakpointPassCount", "Set Breakpoint PassCount", 1);
            
            return Constants.S_OK;
        }

        #endregion
    }
}
